#define FUSE_USE_VERSION 26

/* For pread()/pwrite() */
#define _XOPEN_SOURCE 500

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/xattr.h>
#include <sys/statvfs.h>

#include <boost/thread.hpp>

#include <iostream>

#include "c_client.h"


using std::cout;
using std::cerr;
using std::endl;


namespace dhd
{
namespace client
{
   
   namespace
   {     
      int bad_msg( const PInteraction & pi ) 
      {
          std::cerr << "Malformed message of type: " << pi->reply.msg_type() << std::endl; 
          assert(0); 
          return -EFAULT; 
      }
   }

   

   int dhd_getattr(const char *path, struct stat *stbuf)
   {
       std::cerr << "******* GETATTR *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::GETATTR );
       pi->request.set_path( path );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       if (!pi->reply.has_fstat())
       {
           std::cerr << "#####################################" << std::endl;
           std::cerr << pi->reply.ShortDebugString() << std::endl;
           std::cerr << "#####################################" << std::endl;
       }
       
       
       if (!pi->reply.has_fstat()) return bad_msg( pi );
       
       stbuf->st_size    = pi->reply.fstat().st_size();
       stbuf->st_dev     = pi->reply.fstat().st_dev();
       stbuf->st_ino     = pi->reply.fstat().st_ino();
       stbuf->st_nlink   = pi->reply.fstat().st_nlink();
       stbuf->st_mode    = pi->reply.fstat().st_mode();
       stbuf->st_uid     = pi->reply.fstat().st_uid();
       stbuf->st_gid     = pi->reply.fstat().st_gid();
       stbuf->st_rdev    = pi->reply.fstat().st_rdev();
       stbuf->st_blksize = pi->reply.fstat().st_blksize();
       stbuf->st_blocks  = pi->reply.fstat().st_blocks();
       stbuf->st_atim.tv_sec  = pi->reply.fstat().st_atimespec().tv_sec();
       stbuf->st_atim.tv_nsec = pi->reply.fstat().st_atimespec().tv_nsec();
       stbuf->st_mtim.tv_sec  = pi->reply.fstat().st_mtimespec().tv_sec();
       stbuf->st_mtim.tv_nsec = pi->reply.fstat().st_mtimespec().tv_nsec();
       stbuf->st_ctim.tv_sec  = pi->reply.fstat().st_ctimespec().tv_sec();
       stbuf->st_ctim.tv_nsec = pi->reply.fstat().st_ctimespec().tv_nsec();
       
       return 0;
   }
   
   int dhd_access(const char *path, int mask)
   {
       // TODO
       // 
       return 0;
   }

   int dhd_readlink(const char *path, char *buf, size_t size)
   {
       std::cerr << "******* READLINK *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
    
       pi->request.set_msg_type( dhd::READ_LINK );
       pi->request.set_path( path );
    
       handle_fuse_request( path, pi );    
    
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
    
       if ( !pi->pbuffer || pi->pbuffer->size() == 0 ) return bad_msg( pi );
    
       int ncopy = pi->pbuffer->size() < size - 1 ? pi->pbuffer->size() : size - 1;
       
       memcpy( buf, pi->pbuffer->data(), ncopy );
       
       buf[ ncopy ] = '\0';
       
       return 0;
   }
   
   
   int dhd_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                   off_t offset, struct fuse_file_info *fi)
   {
       std::cerr << "******* READ DIR *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::READ_DIR );
       pi->request.set_path( path );
    
       handle_fuse_request( path, pi );    
    
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
    
       if (!pi->reply.dir_entries_size() >= 1) return bad_msg( pi );
    
       struct stat st;
       memset(&st, 0, sizeof(st));
       for (int i = 0; i < pi->reply.dir_entries_size(); i++ )
       {
           st.st_ino  = pi->reply.dir_entries(i).inode();
           st.st_mode = pi->reply.dir_entries(i).mode() << 12;
           if (filler(buf, pi->reply.dir_entries(i).name().c_str(), &st, 0))
               break;
       }
       
       return 0;
   }

   int dhd_mknod(const char *path, mode_t mode, dev_t rdev)
   {
       std::cerr << "******* MKNOD *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::MKNOD );
       pi->request.set_path( path );
       pi->request.set_mode( mode );
       pi->request.set_rdev( rdev );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_mkdir(const char *path, mode_t mode)
   {
       std::cerr << "******* MKDIR *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::MKDIR );
       pi->request.set_path( path );
       pi->request.set_mode( mode );
       
       handle_fuse_request( path, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_unlink(const char *path)
   {
       std::cerr << "******* UNLINK *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
    
       pi->request.set_msg_type( dhd::UNLINK );
       pi->request.set_path( path );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_rmdir(const char *path)
   {
       std::cerr << "******* RMDIR *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::RMDIR );
       pi->request.set_path( path );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_symlink(const char *from, const char *to)
   {
       std::cerr << "******* SYMLINK *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::SYMLINK );
       pi->request.set_path( to ); // reverse 
       pi->request.set_to( from );
       
       handle_fuse_request( to, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_rename(const char *from, const char *to)
   {
       std::cerr << "******* RENAME *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::RENAME );
       pi->request.set_path( to ); // reverse arguments
       pi->request.set_to( from );
       
       handle_fuse_request( from, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_link(const char *from, const char *to)
   {
       std::cerr << "******* LINK *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::LINK );
       pi->request.set_path( to ); // reverse arguments
       pi->request.set_to( from );
       
       
       handle_fuse_request( from, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_chmod(const char *path, mode_t mode)
   {
       std::cerr << "******* CHMOD *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::CHMOD );
       pi->request.set_path( path );
       pi->request.set_mode( mode );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_chown(const char *path, uid_t uid, gid_t gid)
   {
       std::cerr << "******* CHOWN *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::CHOWN );
       pi->request.set_path( path );
       pi->request.set_uid( uid );
       pi->request.set_gid( gid );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_truncate(const char *path, off_t size)
   {
       std::cerr << "******* TRUNC *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::TRUNCATE );
       pi->request.set_path( path );
       pi->request.set_size( size );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_utimens(const char *path, const struct timespec ts[2])
   {
       std::cerr << "******* UTIME *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::UTIMENS );
       pi->request.set_path( path );
       
       dhd::TimeSpec *rts;
       
       rts = pi->request.add_times();
       rts->set_tv_sec( ts[0].tv_sec );
       rts->set_tv_nsec( ts[0].tv_nsec );
       
       rts = pi->request.add_times();
       rts->set_tv_sec( ts[1].tv_sec );
       rts->set_tv_nsec( ts[1].tv_nsec );
       
       handle_fuse_request( path, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_open(const char *path, struct fuse_file_info *fi)
   {
       std::cerr << "******* OPEN *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::OPEN );
       pi->request.set_path( path );
       pi->request.set_flags( fi->flags );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       if (!pi->reply.has_open_id()) return bad_msg( pi );
       
       fi->fh = pi->reply.open_id();
       
       return 0;
   }
   
   int dhd_read(const char *path, char *buf, size_t size, off_t offset,
                struct fuse_file_info *fi)
   {
       std::cerr << "******* READ *********" << std::endl;
       
       char * pbuff   = buf;
       char * pend    = buf + size;
       int    ntotal  = 0;
       int    nsize   = 0;
       bool   done    = false;
       
       while ( pbuff < pend && ! done )
       {
           PInteraction pi = allocate_interaction();
           
           nsize = size - ntotal;
           
           if ( nsize  > 32 * 1024 )
               nsize = 32 * 1024;
           
           pi->request.set_msg_type( dhd::READ );
           pi->request.set_path( path );
           pi->request.set_offset( offset );
           pi->request.set_size( nsize );
           pi->request.set_open_id( fi->fh );
           
           handle_fuse_request( path, pi );
           
           if ( pi->reply.result() < 0 )
               return pi->reply.result();
           
           if ( pi->pbuffer && pi->pbuffer->size() != 0 )
           {
               if ( nsize != pi->pbuffer->size() )
               {
                   nsize = pi->pbuffer->size();
                   done  = true;
               }
               
               memcpy(pbuff, pi->pbuffer->data(), nsize );
           }
           
           ntotal += nsize;
           pbuff  += nsize;
           offset += nsize;
       }
       
       return ntotal;
   }

   int dhd_write(const char *path, const char *buf, size_t size,
                 off_t offset, struct fuse_file_info *fi)
   {
       std::cerr << "******* WRITE *********" << std::endl;
       
       const char * pbuff   = buf;
       const char * pend    = buf + size;
       int          ntotal  = 0;
       int          nsize   = 0;
       
       while( pbuff < pend )
       {
           PInteraction pi = allocate_interaction();
           
           nsize = size - ntotal;
           
           if ( nsize  > 32 * 1024 )
               nsize = 32 * 1024;
           
           pi->request.set_msg_type( dhd::WRITE );
           pi->request.set_path( path );
           pi->request.set_offset( offset );
           pi->request.set_size( nsize );
           pi->request.set_open_id( fi->fh );
           
           pi->pbuffer = allocate_buffer( nsize );
           
           memcpy( pi->pbuffer->data(), pbuff, nsize );
           
           handle_fuse_request( path, pi );
           
           ntotal += nsize;
           pbuff  += nsize;
           offset += nsize;
       }
       
       return ntotal;
   }

   int dhd_statfs(const char *path, struct statvfs *stbuf)
   {
       std::cerr << "******* STATFS *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::STATFS );
       pi->request.set_path( path );
       
       handle_fuse_request( path, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       if (!pi->reply.has_vstat()) return bad_msg( pi );
       
       stbuf->f_bsize  = pi->reply.vstat().f_bsize();
       stbuf->f_frsize = pi->reply.vstat().f_frsize();
       stbuf->f_blocks = pi->reply.vstat().f_blocks();
       stbuf->f_bfree  = pi->reply.vstat().f_bfree();
       stbuf->f_bavail = pi->reply.vstat().f_bavail();
       stbuf->f_files  = pi->reply.vstat().f_files();
       stbuf->f_ffree  = pi->reply.vstat().f_ffree();
       stbuf->f_favail = pi->reply.vstat().f_favail();
       
       return 0;
   }
   
   int dhd_release(const char *path, struct fuse_file_info *fi)
   {
       std::cerr << "******* RELEASE *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::RELEASE );
       pi->request.set_path( path );
       pi->request.set_open_id( fi->fh );
       
       handle_fuse_request( path, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_fsync(const char *path, int isdatasync,
                 struct fuse_file_info *fi)
   {
       std::cerr << "******* FSYNC *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::FSYNC );
       pi->request.set_path( path );
       pi->request.set_flags( isdatasync ? 1 : 0 );
       pi->request.set_open_id( fi->fh );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }

   int dhd_setxattr(const char *path, const char *name, const char *value,
                    size_t size, int flags)
   {
       std::cerr << "******* XSET *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::SETXATTR );
       pi->request.set_path( path );
       pi->request.set_to( name );
       pi->request.set_flags( flags );
       
       pi->pbuffer = allocate_buffer( size );

       memcpy( pi->pbuffer->data(), value, size );
       
       handle_fuse_request( path, pi );    
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   int dhd_getxattr(const char *path, const char *name, char *value,
                    size_t size)
   {
       std::cerr << "******* XGET *********" << std::endl;
       
       if ( strcmp(name, "security.selinux"        ) == 0 || 
            strcmp(name, "system.posix_acl_access" ) == 0 || 
            strcmp(name, "system.posix_acl_default") == 0 )
       {
           // Due to tremendous number of getxattr calls for SELinux & Posix ACLs,
           // we'll filter these out for now. 
           // TODO: Come up with efficient mechanism for supporting SELinux & Posix ACL XATTR attributes.
           //
           return 0;
       }
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::GETXATTR );
       pi->request.set_path( path );
       pi->request.set_to( name );
       pi->request.set_size( size );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       int ncopy = 0;
       
       if (pi->pbuffer && pi->pbuffer->size() != 0)
       {
           ncopy = pi->pbuffer->size() < size ? pi->pbuffer->size() : size;
           
           memcpy(value, pi->pbuffer->data(), ncopy );
       }
       
       return ncopy;
   }
   
   int dhd_listxattr(const char *path, char *list, size_t size)
   {
       std::cerr << "******* XLIST *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::LISTXATTR );
       pi->request.set_path( path );
       pi->request.set_size( size );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() <= 0 )
           return pi->reply.result();
       
       int ncopy = 0;
       
       if (pi->pbuffer && pi->pbuffer->size() != 0)
       {
           ncopy = pi->pbuffer->size() < size ? pi->pbuffer->size() : size;
           
           memcpy(list, pi->pbuffer->data(), ncopy );
       }
       
       return ncopy;
   }
   
   
   int dhd_removexattr(const char *path, const char *name)
   {
       std::cerr << "******* XREMOVE *********" << std::endl;
       
       PInteraction pi = allocate_interaction();
       
       pi->request.set_msg_type( dhd::REMOVEXATTR );
       pi->request.set_path( path );
       pi->request.set_to( name );
       
       handle_fuse_request( path, pi );
       
       if ( pi->reply.result() != 0 )
           return pi->reply.result();
       
       return 0;
   }
   
   
   void * dhd_init( struct fuse_conn_info *info )
   {
       //info->max_write     = 32 * 1024;
       //info->max_readahead = 32 * 1024;
       return 0;
   }


   int run_fuse_main_loop( int argc, char *argv[] )
   {
       struct fuse_operations dhd_oper;
       
       memset(&dhd_oper, 0, sizeof(dhd_oper));
       
       dhd_oper.getattr     = dhd_getattr;
       dhd_oper.access      = dhd_access;
       dhd_oper.readlink    = dhd_readlink;
       dhd_oper.readdir     = dhd_readdir;
       dhd_oper.mknod       = dhd_mknod;
       dhd_oper.mkdir       = dhd_mkdir;
       dhd_oper.symlink     = dhd_symlink;
       dhd_oper.unlink      = dhd_unlink;
       dhd_oper.rmdir       = dhd_rmdir;
       dhd_oper.rename      = dhd_rename;
       dhd_oper.link        = dhd_link;
       dhd_oper.chmod       = dhd_chmod;
       dhd_oper.chown       = dhd_chown;
       dhd_oper.truncate    = dhd_truncate;
       dhd_oper.utimens     = dhd_utimens;
       dhd_oper.open        = dhd_open;
       dhd_oper.read        = dhd_read;
       dhd_oper.write       = dhd_write;
       dhd_oper.statfs      = dhd_statfs;
       dhd_oper.release     = dhd_release;
       dhd_oper.fsync       = dhd_fsync;
       dhd_oper.setxattr    = dhd_setxattr;
       dhd_oper.getxattr    = dhd_getxattr;
       dhd_oper.listxattr   = dhd_listxattr;
       dhd_oper.removexattr = dhd_removexattr;
       dhd_oper.init        = dhd_init;
       
       return fuse_main(argc, argv, &dhd_oper, NULL);
   }
   

   
}// end namespace dhd::client
}// end namespace dhd


